home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Shared Code / Reusable Source / teutil.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-04-27  |  34.1 KB  |  1,281 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     teutil.c
  4.  
  5.     This reusable module contains miscellaneous TextEdit utility routines.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <ctype.h>
  12.  
  13. #include "def.h"
  14. #include "teutil.h"
  15. #include "memutil.h"
  16. #include "drawutil.h"
  17. #include "tescroll.h"
  18.  
  19.  
  20.  
  21. /*----------------------------------------------------------------------------
  22.     HaveTEOutlineHiliteFeature 
  23.     
  24.     Determine whether or not this system supports the outline highlighting
  25.     feature.
  26.             
  27.     Exit:    function result = true if outline highlighting supported.
  28. ----------------------------------------------------------------------------*/
  29.  
  30. Boolean HaveTEOutlineHiliteFeature (void)
  31. {
  32.     OSErr err = noErr;
  33.     long result;
  34.  
  35.     err = Gestalt(gestaltTextEditVersion, &result);
  36.     return err == noErr && result >= gestaltTE4;
  37. }
  38.  
  39.  
  40.  
  41. /*----------------------------------------------------------------------------
  42.     HaveTEGetHiliteRgn 
  43.     
  44.     Determine whether or not this system supports the GetTEHiliteRgn
  45.     function.
  46.             
  47.     Exit:    function result = true if GetTEHiliteRgn function available.
  48. ----------------------------------------------------------------------------*/
  49.  
  50. Boolean HaveTEGetHiliteRgn (void)
  51. {
  52.     OSErr err = noErr;
  53.     long result;
  54.  
  55.     err = Gestalt(gestaltTEAttr, &result);
  56.     return err == noErr && (result >> gestaltTEHasGetHiliteRgn) & 1 != 0;
  57. }
  58.  
  59.  
  60.  
  61. /*----------------------------------------------------------------------------
  62.     MyTESetText 
  63.     
  64.     Set the text in a TextEdit record, with memory preflighting.
  65.             
  66.     Entry:    textPtr = pointer to text.
  67.             length = length of text.
  68.             theTE = handle to TextEdit record.
  69.     
  70.     Exit:    function result = error code.
  71. ----------------------------------------------------------------------------*/
  72.  
  73. OSErr MyTESetText (Ptr textPtr, long length, TEHandle theTE)
  74. {
  75.     long extraMem;
  76.  
  77.     extraMem = length - (**theTE).teLength;
  78.     if (extraMem > 0 && !MemoryAvailable(extraMem + 2000)) return memFullErr;
  79.     TESetText(textPtr, length, theTE);
  80.     return noErr;
  81. }
  82.  
  83.  
  84.  
  85. /*----------------------------------------------------------------------------
  86.     MyTEInsert 
  87.     
  88.     Insert text in a TextEdit record, with memory preflighting.
  89.             
  90.     Entry:    textPtr = pointer to text.
  91.             length = length of text.
  92.             theTE = handle to TextEdit record.
  93.     
  94.     Exit:    function result = error code.
  95. ----------------------------------------------------------------------------*/
  96.  
  97. OSErr MyTEInsert (Ptr textPtr, long length, TEHandle theTE)
  98. {
  99.     long extraMem;
  100.     
  101.     extraMem = (**theTE).selEnd - (**theTE).selStart + length;
  102.     if (extraMem > 0 && !MemoryAvailable(extraMem + 2000)) return memFullErr;
  103.     TEInsert(textPtr, length, theTE);
  104.     return noErr;
  105. }
  106.  
  107.  
  108.  
  109. /*----------------------------------------------------------------------------
  110.     MyTEGetScrapLen 
  111.     
  112.     Get the text scrap length.
  113.             
  114.     Exit:    function result = scrap length.
  115.                 = 0 if no text in scrap.
  116.                 = 0x8000 if text scrap too big.
  117. ----------------------------------------------------------------------------*/
  118.  
  119. long MyTEGetScrapLen (void)
  120. {
  121.     long len, offset;
  122.     OSErr err = noErr;
  123.     
  124.     len = GetScrap(nil, 'TEXT', &offset);
  125.     if (len < 0) return 0;
  126.     if (len > 0x7fff) return 0x8000;
  127.     err = TEFromScrap();
  128.     if (err == teScrapSizeErr) return 0x8000;
  129.     if (err != noErr) return 0;
  130.     return len;
  131. }
  132.  
  133.  
  134.  
  135. /*----------------------------------------------------------------------------
  136.     CharIsAWord 
  137.     
  138.     Determine whether or not a single character is a "word" (a letter or
  139.     digit).
  140.     
  141.     Entry:    str = pointer to string.
  142.             offset = offset in string of character.
  143.     
  144.     Exit:    function result = true if character is a "word".
  145. ----------------------------------------------------------------------------*/
  146.  
  147. static Boolean CharIsAWord (Ptr str, short offset)
  148. {
  149.     short x, charType, charClass;
  150.  
  151.     x = CharType(str, offset);
  152.     charType = x & smcTypeMask;
  153.     charClass = x & smcClassMask;
  154.     if (charType == smCharAscii || charType == smCharExtAscii) return true;
  155.     if (charType == smCharPunct && charClass == smPunctNumber) return true;
  156.     return false;
  157. }
  158.  
  159.  
  160.  
  161. /*----------------------------------------------------------------------------
  162.     IsWordStart 
  163.     
  164.     Determine whether or not a given offset in a string of text is the 
  165.     beginning of a word.
  166.     
  167.     Entry:    str = pointer to string.
  168.             len = length of string.
  169.             offset = offset in string.
  170.     
  171.     Exit:    function result = true if offset is at beginning of a word.
  172. ----------------------------------------------------------------------------*/
  173.  
  174. static Boolean IsWordStart (Ptr str, short len, short offset)
  175. {
  176.     OffsetTable offsets;
  177.     short offFirst, offSecond;
  178.  
  179.     FindWord(str, len, offset, true, nil, offsets);
  180.     offFirst = offsets[0].offFirst;
  181.     offSecond = offsets[0].offSecond;
  182.     if (offFirst != offset) return false;
  183.     if (offSecond > offFirst + 1) return true;
  184.     return CharIsAWord(str, offFirst);
  185. }
  186.  
  187.  
  188.  
  189. /*----------------------------------------------------------------------------
  190.     IsWordEnd 
  191.     
  192.     Determine whether or not a given offset in a string of text is the 
  193.     end of a word.
  194.     
  195.     Entry:    str = pointer to string.
  196.             len = length of string.
  197.             offset = offset in string.
  198.     
  199.     Exit:    function result = true if offset is at end of a word.
  200. ----------------------------------------------------------------------------*/
  201.  
  202. static Boolean IsWordEnd (Ptr str, short len, short offset)
  203. {
  204.     OffsetTable offsets;
  205.     short offFirst, offSecond;
  206.  
  207.     FindWord(str, len, offset, false, nil, offsets);
  208.     offFirst = offsets[0].offFirst;
  209.     offSecond = offsets[0].offSecond;
  210.     if (offset != offSecond) return false;
  211.     if (offSecond > offFirst + 1) return true;
  212.     return CharIsAWord(str, offFirst);
  213. }
  214.  
  215.  
  216.  
  217. /*----------------------------------------------------------------------------
  218.     MyTEDelete 
  219.     
  220.     Delete text intelligently.
  221.     
  222.     Entry:    theTE = handle to TextEdit record.
  223.             cut = true to cut text to clipboard, false to just delete it.
  224.     
  225.     Exit:    *extraSpaceDeleted = true if an extra space character was deleted
  226.                 before or after the selected text.
  227.     
  228.     Adapted from Apple's "DragText" sample code.
  229. ----------------------------------------------------------------------------*/
  230.  
  231. void MyTEDelete (TEHandle theTE, Boolean cut, Boolean *extraSpaceDeleted)
  232. {    
  233.     short selStart, selEnd, teLength;
  234.     Handle hText;
  235.     char state;
  236.     Boolean selStartsWithWord, selEndsWithWord;
  237.         
  238.     selStart = (**theTE).selStart;
  239.     selEnd = (**theTE).selEnd;
  240.     if (selStart >= selEnd) return;
  241.     teLength = (**theTE).teLength;
  242.     hText = (**theTE).hText;
  243.     
  244.     state = MyHGetState(hText);
  245.     HLock(hText);
  246.     selStartsWithWord = IsWordStart(*hText, teLength, selStart);
  247.     selEndsWithWord = IsWordEnd(*hText, teLength, selEnd);
  248.     MyHSetState(hText, state);
  249.     
  250.     if (cut) {
  251.         TECut(theTE);
  252.         ZeroScrap();
  253.         TEToScrap();
  254.     } else {
  255.         TEDelete(theTE);
  256.     }
  257.     
  258.     *extraSpaceDeleted = false;
  259.     if (selStartsWithWord && selEndsWithWord) {
  260.         if (selStart > 0 && (*hText)[selStart-1] == ' ') {
  261.             TESetSelect(selStart-1, selStart, theTE);
  262.             TEDelete(theTE);
  263.             *extraSpaceDeleted = true;
  264.         } else if (selEnd < teLength && (*hText)[selStart] == ' ') {
  265.             TESetSelect(selStart, selStart+1, theTE);
  266.             TEDelete(theTE);
  267.             *extraSpaceDeleted = true;
  268.         }
  269.     }
  270. }
  271.  
  272.  
  273.  
  274. /*----------------------------------------------------------------------------
  275.     MyTECut 
  276.     
  277.     Cut text intelligently.
  278.     
  279.     Entry:    theTE = handle to TextEdit record.
  280.     
  281.     Adapted from Apple's "DragText" sample code.
  282. ----------------------------------------------------------------------------*/
  283.  
  284. void MyTECut (TEHandle theTE)
  285. {    
  286.     Boolean extraSpaceDeleted;
  287.  
  288.     MyTEDelete(theTE, true, &extraSpaceDeleted);
  289. }
  290.  
  291.  
  292.  
  293. /*----------------------------------------------------------------------------
  294.     MyTECopy 
  295.     
  296.     Copy text.
  297.             
  298.     Entry:    theTE = handle to TextEdit record.
  299. ----------------------------------------------------------------------------*/
  300.  
  301. void MyTECopy (TEHandle theTE)
  302. {
  303.     TECopy(theTE);
  304.     ZeroScrap();
  305.     TEToScrap();
  306. }
  307.  
  308.  
  309.  
  310. /*----------------------------------------------------------------------------
  311.     MyTEPaste 
  312.     
  313.     Paste text intelligently.
  314.             
  315.     Entry:    text = pointer to text to be pasted, or nil if the
  316.                 text to be pasted is in the TextEdit scrap.
  317.             len = length of text to be pasted.
  318.             theTE = handle to TextEdit record.
  319.             maxLen = maximum legal length for this TextEdit field.
  320.             
  321.     Exit:    *extraSpaceAddedInFront = true if extra space character added
  322.                 in front of pasted text.
  323.     
  324.     Adapted from Apple's "DragText" sample code.
  325. ----------------------------------------------------------------------------*/
  326.  
  327. void MyTEPaste (Ptr text, short len, TEHandle theTE, short maxLen, 
  328.     Boolean *extraSpaceAddedInFront)
  329. {
  330.     Handle h;
  331.     char state;
  332.     Boolean newTextStartsWithWord, newTextEndsWithWord;
  333.     short selStart, selEnd, teLength;
  334.     Handle hText;
  335.     Boolean wordPreceedsSel, wordFollowsSel;
  336.     Boolean roomForExtraSpace;
  337.     
  338.     *extraSpaceAddedInFront = false;
  339.     
  340.     if (text == nil) len = MyTEGetScrapLen();
  341.      if (len == 0) return;
  342.     
  343.     if (text == nil) {
  344.         h = TEScrapHandle();
  345.         state = MyHGetState(h);
  346.         HLock(h);
  347.         newTextStartsWithWord = IsWordStart(*h, len, 0);
  348.         newTextEndsWithWord = IsWordEnd(*h, len, len);
  349.         MyHSetState(h, state);
  350.     } else {
  351.         newTextStartsWithWord = IsWordStart(text, len, 0);
  352.         newTextEndsWithWord = IsWordEnd(text, len, len);
  353.     }
  354.     
  355.     if (newTextStartsWithWord && newTextEndsWithWord) {
  356.         
  357.         selStart = (**theTE).selStart;
  358.         selEnd = (**theTE).selEnd;
  359.         teLength = (**theTE).teLength;
  360.         hText = (**theTE).hText;
  361.         
  362.         state = MyHGetState(hText);
  363.         HLock(hText);
  364.         wordPreceedsSel = selStart > 0 && IsWordEnd(*hText, teLength, selStart);
  365.         wordFollowsSel = selEnd < teLength && IsWordStart(*hText, teLength, selEnd);
  366.         MyHSetState(hText, state);
  367.         
  368.         roomForExtraSpace = teLength - (selEnd - selStart) + len < maxLen;
  369.         
  370.         if (roomForExtraSpace) {
  371.             if (wordPreceedsSel) {
  372.                 TEKey(' ', theTE);
  373.                 *extraSpaceAddedInFront = true;
  374.             } else if (wordFollowsSel) {
  375.                 TEKey(' ', theTE);
  376.                 TESetSelect(selStart, selStart, theTE);
  377.             }
  378.         }
  379.         
  380.     }
  381.     
  382.     if (text == nil) {
  383.         TEPaste(theTE);
  384.     } else {
  385.         TEDelete(theTE);
  386.         TEInsert(text, len, theTE);
  387.     }
  388. }
  389.  
  390.  
  391.  
  392. /*----------------------------------------------------------------------------
  393.     PtInTEHiliteRgn 
  394.     
  395.     Determine whether or not a point is in the current TextEdit hilite
  396.     region.
  397.             
  398.     Entry:    where = point in local coords.
  399.             theTE = handle to TextEdit record.
  400.             
  401.     Exit:    function result = true if point is in the hilite region.
  402. ----------------------------------------------------------------------------*/
  403.  
  404. Boolean PtInTEHiliteRgn (Point where, TEHandle theTE)
  405. {
  406.     Boolean result = false;
  407.     RgnHandle rgn = nil;
  408.     OSErr err = noErr;
  409.     
  410.     if (!HaveTEGetHiliteRgn()) return false;
  411.     rgn = NewRgn();
  412.     err = TEGetHiliteRgn(rgn, theTE);
  413.     if (err != noErr) goto exit;
  414.     result = PtInRgn(where, rgn);
  415.     
  416. exit:
  417.  
  418.     if (rgn != nil) DisposeRgn(rgn);
  419.     return result;
  420. }
  421.  
  422.  
  423.  
  424. /*----------------------------------------------------------------------------
  425.     SubtractTEHiliteRgn 
  426.     
  427.     Subtract a TE hilite region from another region.
  428.             
  429.     Entry:    rgn = region handle, in global coordinates.
  430.             theTE = handle to TextEdit record.
  431. ----------------------------------------------------------------------------*/
  432.  
  433. void SubtractTEHiliteRgn (RgnHandle rgn, TEHandle theTE)
  434. {
  435.     RgnHandle hiliteRgn = nil;
  436.     OSErr err = noErr;
  437.     
  438.     if (!HaveTEGetHiliteRgn()) return;
  439.     hiliteRgn = NewRgn();
  440.     err = TEGetHiliteRgn(hiliteRgn, theTE);
  441.     if (err != noErr) goto exit;
  442.     LocalToGlobalRgn(hiliteRgn);
  443.     DiffRgn(rgn, hiliteRgn, rgn);
  444.     
  445. exit:
  446.  
  447.     if (hiliteRgn != nil) DisposeRgn(hiliteRgn);
  448. }
  449.  
  450.  
  451.  
  452. /*----------------------------------------------------------------------------
  453.     DrawTECaret 
  454.     
  455.     Draw a caret at a given offset into a TextEdit field.
  456.             
  457.     Entry:    offset = offset into field.
  458.             theTE = handle to TextEdit record.
  459.             
  460.     If a caret is already drawn at this location, it is erased.
  461.     
  462.     Adapted from Apple's "DragText" sample code.
  463. ----------------------------------------------------------------------------*/
  464.  
  465. void DrawTECaret (short offset, TEHandle theTE)
  466. {
  467.     short lineHeight, teLength;
  468.     Handle hText;
  469.     Point where;
  470.     short pnMode;
  471.     
  472.     lineHeight = (**theTE).lineHeight;
  473.     teLength = (**theTE).teLength;
  474.     hText = (**theTE).hText;
  475.     where = TEGetPoint(offset, theTE);
  476.     if (offset == teLength && teLength > 0 && (*hText)[teLength - 1] == CR)
  477.         where.v += lineHeight;
  478.     pnMode = qd.thePort->pnMode;
  479.     PenMode(patXor);
  480.     MoveTo(where.h - 1, where.v - 1);
  481.     Line(0, 1 - lineHeight);
  482.     PenMode(pnMode);
  483. }
  484.  
  485.  
  486.  
  487. /*----------------------------------------------------------------------------
  488.     TEIsFrontOfLine 
  489.     
  490.     Determine whether an offset in a TextEdit field is at the beginning of a
  491.     line.
  492.             
  493.     Entry:    offset = offset into field.
  494.             theTE = handle to TextEdit record.
  495.             
  496.     Exit:    function result = true if offset is at beginning of line.
  497.     
  498.     Adapted from Apple's "DragText" sample code.
  499. ----------------------------------------------------------------------------*/
  500.  
  501. Boolean TEIsFrontOfLine (short offset, TEHandle theTE)
  502. {    
  503.     short line = 0, teLength;
  504.     Handle hText;
  505.     short *lineStarts;
  506.  
  507.     teLength = (**theTE).teLength;
  508.     hText = (**theTE).hText;
  509.     lineStarts = (**theTE).lineStarts;
  510.  
  511.     if (teLength == 0) return true;
  512.  
  513.     if (offset >= teLength)
  514.         return (*hText)[teLength - 1] == CR;
  515.  
  516.     while (lineStarts[line] < offset) line++;
  517.  
  518.     return lineStarts[line] == offset;
  519. }
  520.  
  521.  
  522.  
  523. /*----------------------------------------------------------------------------
  524.     MyTEGetOffset 
  525.     
  526.     Get the offset in the text corresponding to a point in a TextEdit field.
  527.             
  528.     Entry:    where = point in local coords.
  529.             theTE = handle to TextEdit record.
  530.             
  531.     Exit:    function result = offset into text.
  532.     
  533.     Adapted from Apple's "DragText" sample code.
  534. ----------------------------------------------------------------------------*/
  535.  
  536. short MyTEGetOffset (Point where, TEHandle theTE)
  537. {
  538.     short offset;
  539.     Handle hText;
  540.     char prevChar;
  541.     Point pt;
  542.     
  543.     offset = TEGetOffset(where, theTE);
  544.     hText = (**theTE).hText;
  545.     if (!TEIsFrontOfLine(offset, theTE)) return offset;
  546.     if (offset == 0) return offset;
  547.     prevChar = (*hText)[offset-1];
  548.     if (prevChar == CR) return offset;
  549.     if (!isLWSP(prevChar)) return offset;
  550.     pt = TEGetPoint(offset-1, theTE);
  551.     if (pt.h >= where.h) return offset;
  552.     return offset - 1;
  553. }
  554.  
  555.  
  556.  
  557. /*----------------------------------------------------------------------------
  558.     MyTEActivate 
  559.     
  560.     Activate a TextEdit field, but only if the window containing the field
  561.     is active.
  562.             
  563.     Entry:    theTE = handle to TextEdit record.
  564. ----------------------------------------------------------------------------*/
  565.  
  566. void MyTEActivate (TEHandle theTE)
  567. {
  568.     WindowPeek windPeek;
  569.  
  570.     windPeek = (WindowPeek)(**theTE).inPort;
  571.     if (windPeek->hilited) TEActivate(theTE);
  572. }
  573.  
  574.  
  575.  
  576. /*----------------------------------------------------------------------------
  577.     GetBol 
  578.     
  579.     Get the beginning of the line containing a character.
  580.             
  581.     Entry:    offset = offset into text of character.
  582.             clikStuff = 0 if offset on line boundary is at end of
  583.                 previous line, non-zero if offset on line boundary
  584.                 is at beginning of next line. 
  585.             theTE = handle to TextEdit record.
  586.             
  587.     Exit:    function result = offset into text of beginning of line 
  588.                 containing the character.
  589. ----------------------------------------------------------------------------*/
  590.  
  591. static short GetBol (short offset, short clikStuff, TEHandle theTE)
  592. {
  593.     short *lineStarts;
  594.     short teLength;
  595.  
  596.     teLength = (**theTE).teLength;
  597.     lineStarts = (**theTE).lineStarts;
  598.     if (offset <= 0) {
  599.         return 0;
  600.     } else if (offset >= teLength) {
  601.         offset = teLength;
  602.     }
  603.     while (*lineStarts < offset) lineStarts++;
  604.     if (*lineStarts == offset && clikStuff != 0) {
  605.         return *lineStarts;
  606.     } else {
  607.         return *(lineStarts-1);
  608.     }
  609. }
  610.  
  611.  
  612.  
  613. /*----------------------------------------------------------------------------
  614.     GetEol 
  615.     
  616.     Get the end of the line containing a character.
  617.             
  618.     Entry:    offset = offset into text of character.
  619.             clikStuff = 0 if offset on line boundary is at end of
  620.                 previous line, non-zero if offset on line boundary
  621.                 is at beginning of next line. 
  622.             theTE = handle to TextEdit record.
  623.             
  624.     Exit:    function result = offset into text of end of line 
  625.                 containing the character.
  626. ----------------------------------------------------------------------------*/
  627.  
  628. static short GetEol (short offset, short clikStuff, TEHandle theTE)
  629. {
  630.     short *lineStarts;
  631.     short teLength;
  632.  
  633.     teLength = (**theTE).teLength;
  634.     lineStarts = (**theTE).lineStarts;
  635.     if (offset <= 0) {
  636.         offset = 0;
  637.         clikStuff = 0xffff;
  638.     } else if (offset >= teLength) {
  639.         return teLength;
  640.     }
  641.     while (*lineStarts < offset) lineStarts++;
  642.     if (*lineStarts == offset && clikStuff != 0) {
  643.         return *(lineStarts+1);
  644.     } else {
  645.         return *lineStarts;
  646.     }
  647. }
  648.  
  649.  
  650.  
  651. /*----------------------------------------------------------------------------
  652.     GetTEPageHeight 
  653.     
  654.     Get the page height for a TextEdit field - the number of lines in
  655.     the view rectangle minus 1.
  656.             
  657.     Entry:    theTE = handle to TextEdit record.
  658.             
  659.     Exit:    function result = page height
  660. ----------------------------------------------------------------------------*/
  661.  
  662. short GetTEPageHeight (TEHandle theTE)
  663. {
  664.     short lineHeight;
  665.     Rect viewRect;
  666.  
  667.     lineHeight = (**theTE).lineHeight;
  668.     viewRect = (**theTE).viewRect;
  669.     return (viewRect.bottom - viewRect.top) / lineHeight - 1;
  670. }
  671.  
  672.  
  673.  
  674. /*----------------------------------------------------------------------------
  675.     GetBop 
  676.     
  677.     Get the beginning of the page.
  678.             
  679.     Entry:    offset = offset into text of character.
  680.             theTE = handle to TextEdit record.
  681.             pageHeight = number of lines in a page, or 0 to
  682.                 compute this based on the assumption that the
  683.                 view rectangle of the TextEdit field is a page.
  684.             
  685.     Exit:    function result = offset into text of beginning of page 
  686.                 containing the character, or beginning of previous
  687.                 page if already at top of page.
  688. ----------------------------------------------------------------------------*/
  689.  
  690. static short GetBop (short offset, TEHandle theTE, short pageHeight)
  691. {
  692.     Rect viewRect;
  693.     Point where;
  694.     short bop;
  695.     short *lineStarts, *p;
  696.     
  697.     viewRect = (**theTE).viewRect;
  698.     where.h = viewRect.left;
  699.     where.v = viewRect.top + 2;
  700.     bop = MyTEGetOffset(where, theTE);
  701.     if (bop < offset) return bop;
  702.     if (pageHeight == 0) pageHeight = GetTEPageHeight(theTE);
  703.     lineStarts = (**theTE).lineStarts;
  704.     p = lineStarts;
  705.     while (*p < bop) p++;
  706.     p -= pageHeight;
  707.     if (p < lineStarts) p = lineStarts;
  708.     return *p;
  709. }
  710.  
  711.  
  712.  
  713. /*----------------------------------------------------------------------------
  714.     GetEop 
  715.     
  716.     Get the end of the page.
  717.             
  718.     Entry:    offset = offset into text of character.
  719.             theTE = handle to TextEdit record.
  720.             pageHeight = number of lines in a page, or 0 to
  721.                 compute this based on the assumption that the
  722.                 view rectangle of the TextEdit field is a page.
  723.             
  724.     Exit:    function result = offset into text of end of page 
  725.                 containing the character, or end of next
  726.                 page if already at end of page.
  727. ----------------------------------------------------------------------------*/
  728.  
  729. static short GetEop (short offset, TEHandle theTE, short pageHeight)
  730. {
  731.     Rect viewRect;
  732.     Point where;
  733.     short eop;
  734.     short *lineStarts, *p, *lineStartsEnd;
  735.     short teLength;
  736.     Handle hText;
  737.     
  738.     viewRect = (**theTE).viewRect;
  739.     where.h = viewRect.right - 2;
  740.     where.v = viewRect.bottom - 2;
  741.     eop = MyTEGetOffset(where, theTE);
  742.     teLength = (**theTE).teLength;
  743.     hText = (**theTE).hText;
  744.     if (eop < teLength && *(*hText + eop) == CR) eop++;
  745.     if (offset < teLength && *(*hText + offset) == CR) offset++;
  746.     if (eop > offset) return eop;
  747.     if (pageHeight == 0) pageHeight = GetTEPageHeight(theTE);
  748.     lineStarts = (**theTE).lineStarts;
  749.     lineStartsEnd = lineStarts + (**theTE).nLines;
  750.     p = lineStarts;
  751.     while (*p < eop) p++;
  752.     p += pageHeight;
  753.     if (p > lineStartsEnd) p = lineStartsEnd;
  754.     return *p;
  755. }
  756.  
  757.  
  758.  
  759. /*----------------------------------------------------------------------------
  760.     GetPixelsFromBolGivenOffset 
  761.     
  762.     Get the the number of pixels from the beginning of a line to a given
  763.     character in the line.
  764.  
  765.     Entry:    bol = offset into text of beginning of line containing the
  766.                 character.
  767.             offset = offset into text of character.
  768.             theTE = handle to TextEdit record.
  769.             
  770.     Exit:    function result = number of pixels from the beginning of the
  771.                 line to the character.
  772. ----------------------------------------------------------------------------*/
  773.  
  774. static short GetPixelsFromBolGivenOffset (short bol, short offset, TEHandle theTE)
  775. {
  776.     Handle hText;
  777.     char state;
  778.     short result;
  779.  
  780.     hText = (**theTE).hText;
  781.     state = MyHGetState(hText);
  782.     HLock(hText);
  783.     result = TextWidth(*hText, bol, offset-bol);
  784.     MyHSetState(hText, state);
  785.     return result;
  786. }
  787.  
  788.  
  789.  
  790. /*----------------------------------------------------------------------------
  791.     GetOffsetGivenPixelsFromBol 
  792.     
  793.     Get the offset of the character which is a given number of pixels into 
  794.     a line.
  795.             
  796.     Entry:    bol = offset into text of beginning of line.
  797.             pixelsFromBol = number of pixels.
  798.             theTE = handle to TextEdit record.
  799.             
  800.     Exit:    function result = offset into text of the character which is
  801.                 the given number of pixels into the line.
  802. ----------------------------------------------------------------------------*/
  803.  
  804. static short GetOffsetGivenPixelsFromBol (short bol, short pixelsFromBol, TEHandle theTE)
  805. {
  806.     Handle hText;
  807.     char state;
  808.     short result;
  809.     short eol;
  810.  
  811.     hText = (**theTE).hText;
  812.     state = MyHGetState(hText);
  813.     HLock(hText);
  814.     
  815.     eol = GetEol(bol, 0xffff, theTE);
  816.     result = bol;
  817.     while (result < eol && TextWidth(*hText, bol, result - bol) < pixelsFromBol) result++;
  818.     
  819.     MyHSetState(hText, state);
  820.     return result;
  821. }
  822.  
  823.  
  824.  
  825. /*----------------------------------------------------------------------------
  826.     MyGetClikStuff 
  827.     
  828.     Get the clikStuff field from an edit record, adjusted if the insertion
  829.     point follows a final CR in the field. 
  830.             
  831.     Entry:    theTE = handle to TextEdit record.
  832.             
  833.     Exit:    function result = adjusted clikStuff field.
  834. ----------------------------------------------------------------------------*/
  835.  
  836. short MyGetClikStuff (TEHandle theTE)
  837. {
  838.     short selStart, selEnd, clikStuff, teLength;
  839.     Handle hText;
  840.     
  841.     selStart = (**theTE).selStart;
  842.     selEnd = (**theTE).selEnd;
  843.     clikStuff = (**theTE).clikStuff;
  844.     hText = (**theTE).hText;
  845.     teLength = (**theTE).teLength;
  846.     if (selStart == selEnd && selStart == teLength) {
  847.         if (clikStuff == 0 && selStart > 0 && *(*hText + selStart - 1) == CR) {
  848.             (**theTE).clikStuff = clikStuff = 0xffff;
  849.         } else if (clikStuff != 0 && (selStart == 0 || *(*hText + selStart - 1) != CR)) {
  850.             (**theTE).clikStuff = clikStuff = 0;
  851.         }
  852.     }
  853.     return clikStuff;
  854. }
  855.  
  856.  
  857.  
  858. /*----------------------------------------------------------------------------
  859.     MyGetTEHiliteOrCaretRgn 
  860.     
  861.     Get the current hilite region for a TextEdit field if there is
  862.     a selection range, or a region containing the caret if there
  863.     is no selection range.
  864.             
  865.     Entry:    rgn = handle to region.
  866.             theTE = handle to TextEdit record.
  867.                 
  868.     Exit:    rgn modified to contain hilite or caret region.
  869. ----------------------------------------------------------------------------*/
  870.  
  871. static void MyGetTEHiliteOrCaretRgn (RgnHandle rgn, TEHandle theTE)
  872. {
  873.     short selStart, selEnd, lineHeight;
  874.     Point where;
  875.     
  876.     selStart = (**theTE).selStart;
  877.     selEnd = (**theTE).selEnd;
  878.     lineHeight = (**theTE).lineHeight;
  879.     if (selStart == selEnd) {
  880.         where = TEGetPoint(selStart, theTE);
  881.         SetRectRgn(rgn, where.h - 1, where.v - lineHeight, where.h + 1, where.v);
  882.     } else {
  883.         TEGetHiliteRgn(rgn, theTE);
  884.     }
  885. }
  886.  
  887.  
  888.  
  889. /*----------------------------------------------------------------------------
  890.     TEArrowKey 
  891.     
  892.     Handle arrow keys in TextEdit windows.
  893.             
  894.     Entry:    theChar = leftArrow, rightArrow, upArrow, or downArrow.
  895.             modifiers = modifiers field from event record.
  896.             theTE = handle to TextEdit record.
  897.             pageHeight = number of lines in a page, or 0 to have TEArrowKey
  898.                 compute this itself based on the assumption that the
  899.                 view rectangle of the TextEdit field is a page.
  900.             prevEvent = pointer to previous event.
  901.                 
  902.     Exit:    selection range adjusted in the TextEdit record.
  903.             *scrollIntoView = offset into TextEdit record which
  904.                 should be scrolled into view.
  905.     
  906.     Warning: The function proves that if you hack long enough, you can make
  907.     TextEdit do anything you want it to do, but only at the risk of tying 
  908.     your brain into complete knots. This code is incredibly complicated and 
  909.     sensitive.
  910. ----------------------------------------------------------------------------*/
  911.  
  912. void TEArrowKey (char theChar, short modifiers, TEHandle theTE, 
  913.     short pageHeight, EventRecord *prevEvent, short *scrollIntoView)
  914. {
  915.     short selStart, selEnd, teLength, offset, bol, eol;
  916.     Boolean shift, option, command;
  917.     Boolean prevWasArrowKey;
  918.     char prevChar;
  919.     short prevModifiers;
  920.     Boolean isUpDown;
  921.     Boolean shiftArrowSequence, upDownArrowSequence;
  922.     Handle hText;
  923.     Boolean haveTEOutlineHilite;
  924.     short savedOutlineHilite;
  925.     OffsetTable wordOffsets;
  926.     char state;
  927.     char c;
  928.     short newSelStart, newSelEnd, oldOffset;
  929.     static RgnHandle selRgnOld = nil, selRgnNew = nil;
  930.     static short anchor = 0;
  931.     static short upDownPixelsFromBol = 0;
  932.     static short clikStuff = 0xffff;
  933.     
  934.     selStart = (**theTE).selStart;
  935.     selEnd = (**theTE).selEnd;
  936.     teLength = (**theTE).teLength;
  937.     hText = (**theTE).hText;
  938.     shift = (modifiers & shiftKey) != 0;
  939.     option = (modifiers & optionKey) != 0;
  940.     command = (modifiers & cmdKey) != 0;
  941.     isUpDown = theChar == upArrow || theChar == downArrow;
  942.     prevChar = prevEvent->message & 0xff;
  943.     prevModifiers = prevEvent->modifiers;
  944.     prevWasArrowKey = (prevEvent->what == keyDown || prevEvent->what == autoKey) &&
  945.         IsArrowKey(prevChar);
  946.     shiftArrowSequence = shift && prevWasArrowKey && 
  947.         (prevModifiers & shiftKey) != 0; 
  948.     upDownArrowSequence = isUpDown && prevWasArrowKey && !option && !command && 
  949.         (prevChar == upArrow || prevChar == downArrow) &&
  950.         (prevModifiers & optionKey) == 0 &&
  951.         (prevModifiers & cmdKey) == 0;
  952.     
  953.     if (shift && !shiftArrowSequence)
  954.         anchor = theChar == upArrow || theChar == leftArrow ?
  955.             selEnd : selStart;
  956.         
  957.     if (shiftArrowSequence) {
  958.         offset = anchor == selStart ? selEnd : selStart;
  959.     } else {
  960.         offset = theChar == upArrow || theChar == leftArrow ? selStart : selEnd;
  961.     }
  962.     
  963.     oldOffset = offset;
  964.     
  965.     if (!option && !command) {
  966.     
  967.         if (isUpDown && !upDownArrowSequence) {
  968.             clikStuff = selStart == selEnd ? MyGetClikStuff(theTE) : 0xffff;
  969.             bol = GetBol(offset, clikStuff, theTE);
  970.             upDownPixelsFromBol = GetPixelsFromBolGivenOffset(bol, offset, theTE);
  971.         }
  972.     
  973.         switch (theChar) {
  974.             case leftArrow:
  975.                 if (shift || selStart == selEnd) offset--;
  976.                 clikStuff = 0xffff;
  977.                 break;
  978.             case rightArrow:
  979.                 if (shift || selStart == selEnd) offset++;
  980.                 clikStuff = 0xffff;
  981.                 break;
  982.             case upArrow:
  983.                 if (shift && upDownPixelsFromBol > 0 && 
  984.                     offset < teLength && *(*hText+offset) == CR) offset++;
  985.                 if (shift && offset != anchor) {
  986.                     clikStuff = upDownPixelsFromBol > 0 ? 0 : 0xffff;
  987.                 } else {
  988.                     clikStuff = MyGetClikStuff(theTE);
  989.                 }
  990.                 bol = GetBol(offset, clikStuff, theTE);
  991.                 if (bol > 0) {
  992.                     bol = GetBol(bol, 0, theTE);
  993.                     offset = GetOffsetGivenPixelsFromBol(bol, upDownPixelsFromBol, theTE);
  994.                 } else if (offset == oldOffset) {
  995.                     offset = 0;
  996.                 }
  997.                 clikStuff = offset > bol ? 0 : 0xffff;
  998.                 if (shift && upDownPixelsFromBol > 0 && 
  999.                     offset > 0 && *(*hText+offset-1) == CR) offset--;
  1000.                 break;
  1001.             case downArrow:
  1002.                 if (shift && upDownPixelsFromBol > 0 && 
  1003.                     offset < teLength && *(*hText+offset) == CR) offset++;
  1004.                 if (shift && offset != anchor) {
  1005.                     clikStuff = upDownPixelsFromBol > 0 ? 0 : 0xffff;
  1006.                 } else {
  1007.                     clikStuff = MyGetClikStuff(theTE);
  1008.                 }
  1009.                 bol = GetEol(offset, clikStuff, theTE);
  1010.                 if (bol < teLength || (shift && upDownPixelsFromBol == 0) ||
  1011.                     (teLength > 0 && *(*hText + teLength - 1) == CR))
  1012.                         offset = GetOffsetGivenPixelsFromBol(bol, upDownPixelsFromBol, theTE);
  1013.                 if (bol >= teLength && offset == oldOffset) {
  1014.                     offset = teLength;
  1015.                 }
  1016.                 clikStuff = offset > bol ? 0 : 0xffff;
  1017.                 if (shift && upDownPixelsFromBol > 0 && 
  1018.                     offset > 0 && *(*hText+offset-1) == CR) offset--;
  1019.                 break;
  1020.         }
  1021.  
  1022.     } else if (option && !command) {
  1023.     
  1024.         switch (theChar) {
  1025.             case leftArrow:
  1026.                 offset--;
  1027.                 while (offset >= 0) {
  1028.                     c = *(*hText + offset);
  1029.                     if (c < 0 || isalnum(c)) break;
  1030.                     offset--;
  1031.                 }
  1032.                 offset++;
  1033.                 state = MyHGetState(hText);
  1034.                 HLock(hText);
  1035.                 FindWord(*hText, teLength, offset, false, nil, wordOffsets);
  1036.                 MyHSetState(hText, state);
  1037.                 offset = wordOffsets[0].offFirst;
  1038.                 clikStuff = 0xffff;
  1039.                 break;
  1040.             case rightArrow:
  1041.                 while (offset < teLength) {
  1042.                     c = *(*hText + offset);
  1043.                     if (c < 0 || isalnum(c)) break;
  1044.                     offset++;
  1045.                 }
  1046.                 state = MyHGetState(hText);
  1047.                 HLock(hText);
  1048.                 FindWord(*hText, teLength, offset, true, nil, wordOffsets);
  1049.                 MyHSetState(hText, state);
  1050.                 offset = wordOffsets[0].offSecond;
  1051.                 clikStuff = 0;
  1052.                 break;
  1053.             case upArrow:
  1054.                 offset--;
  1055.                 while (offset >= 0 && isLWSPorCR(*(*hText + offset))) offset--;
  1056.                 while (offset >= 0) {
  1057.                     while (offset >= 0 && !isLWSPorCR(*(*hText + offset))) offset--;
  1058.                     if (offset < 0) break;
  1059.                     offset--;
  1060.                     while (offset >= 0 && isLWSP(*(*hText + offset))) offset--;
  1061.                     if (offset < 0 || *(*hText + offset) == CR) break;
  1062.                 }
  1063.                 offset++;
  1064.                 while (offset < teLength && isLWSPorCR(*(*hText + offset))) offset++;
  1065.                 clikStuff = 0xffff;
  1066.                 break;
  1067.             case downArrow:
  1068.                 while (offset < teLength && isLWSPorCR(*(*hText + offset))) offset++;
  1069.                 while (offset < teLength) {
  1070.                     while (offset < teLength && *(*hText + offset) != CR) offset++;
  1071.                     if (offset >= teLength) break;
  1072.                     offset++;
  1073.                     if (isLWSPorCR(*(*hText + offset))) break;
  1074.                 }
  1075.                 if (offset < teLength) {
  1076.                     offset--;
  1077.                     while (offset >= 0 && isLWSPorCR(*(*hText + offset))) offset--;
  1078.                     offset++;
  1079.                 }
  1080.                 clikStuff = 0;
  1081.                 break;
  1082.         }
  1083.         
  1084.     } else if (!option && command) {
  1085.     
  1086.         switch (theChar) {
  1087.             case leftArrow:
  1088.                 clikStuff = selStart == selEnd ? MyGetClikStuff(theTE) : 0xffff;
  1089.                 bol = GetBol(offset, clikStuff, theTE);
  1090.                 if (offset > bol) {
  1091.                     offset = bol;
  1092.                 } else {
  1093.                     offset = GetBol(offset, 0, theTE);
  1094.                 }
  1095.                 clikStuff = 0xffff;
  1096.                 break;
  1097.             case rightArrow:
  1098.                 clikStuff = selStart == selEnd ? MyGetClikStuff(theTE) : 0xffff;
  1099.                 eol = GetEol(offset, clikStuff, theTE);
  1100.                 if (offset < eol) {
  1101.                     if (offset == eol-1 && *(*hText + offset) == CR) {
  1102.                         offset = GetEol(eol, 0xffff, theTE);
  1103.                     } else {
  1104.                         offset = eol;
  1105.                     }
  1106.                 } else {
  1107.                     offset = GetEol(offset, 0xffff, theTE);
  1108.                 }
  1109.                 clikStuff = 0;
  1110.                 break;
  1111.             case upArrow:
  1112.                 offset = GetBop(offset, theTE, pageHeight);
  1113.                 clikStuff = 0xffff;
  1114.                 break;
  1115.             case downArrow:
  1116.                 offset = GetEop(offset, theTE, pageHeight);
  1117.                 clikStuff = 0;
  1118.                 break;
  1119.         }
  1120.     
  1121.     } else { /* option && command */
  1122.     
  1123.         switch (theChar) {
  1124.             case upArrow:
  1125.                 offset = 0;
  1126.                 clikStuff = 0xffff;
  1127.                 break;
  1128.             case downArrow:
  1129.                 offset = teLength;
  1130.                 clikStuff = 0;
  1131.                 break;
  1132.         }
  1133.     
  1134.     }
  1135.     
  1136.     if ((!shift || anchor == offset) && clikStuff == 0 && 
  1137.         offset > 0 && *(*hText + offset - 1) == CR)
  1138.     {
  1139.         offset--;
  1140.         clikStuff = 0xffff;
  1141.     }
  1142.     
  1143.     if (offset < 0) {
  1144.         offset = 0;
  1145.     } else if (offset > teLength) {
  1146.         offset = teLength;
  1147.     }
  1148.     
  1149.     if (!shift) {
  1150.         if (MyGetClikStuff(theTE) != clikStuff && selStart == selEnd) {
  1151.             haveTEOutlineHilite = HaveTEOutlineHiliteFeature();
  1152.             if (haveTEOutlineHilite) 
  1153.                 savedOutlineHilite = TEFeatureFlag(teFOutlineHilite, TEBitClear, theTE);
  1154.             TEDeactivate(theTE);
  1155.             (**theTE).clikStuff = clikStuff;
  1156.             TESetSelect(offset, offset, theTE);
  1157.             TEActivate(theTE);
  1158.             if (haveTEOutlineHilite)
  1159.                 TEFeatureFlag(teFOutlineHilite, savedOutlineHilite, theTE);
  1160.         } else {
  1161.             (**theTE).clikStuff = clikStuff;
  1162.             TESetSelect(offset, offset, theTE);
  1163.         }
  1164.     } else {
  1165.         if (offset <= anchor) {
  1166.             newSelStart = offset;
  1167.             newSelEnd = anchor;
  1168.         } else {
  1169.             newSelStart = anchor;
  1170.             newSelEnd = offset;
  1171.         }
  1172.         if (HaveTEGetHiliteRgn()) {
  1173.             if (selRgnOld == nil) selRgnOld = NewRgn();
  1174.             if (selRgnNew == nil) selRgnNew = NewRgn();
  1175.             MyGetTEHiliteOrCaretRgn(selRgnOld, theTE);
  1176.             (**theTE).selStart = newSelStart;
  1177.             (**theTE).selEnd = newSelEnd;
  1178.             MyGetTEHiliteOrCaretRgn(selRgnNew, theTE);
  1179.             (**theTE).selStart = selStart;
  1180.             (**theTE).selEnd = selEnd;
  1181.             if (selStart == selEnd || newSelStart == newSelEnd) {
  1182.                 UnionRgn(selRgnNew, selRgnOld, selRgnNew);
  1183.             } else {
  1184.                 XorRgn(selRgnNew, selRgnOld, selRgnNew);
  1185.             }
  1186.             SetClip(selRgnNew);
  1187.         }
  1188.         TESetSelect(newSelStart, newSelEnd, theTE);
  1189.         ClipRect(&qd.thePort->portRect);
  1190.     }
  1191.     
  1192.     *scrollIntoView = offset;
  1193. }
  1194.  
  1195.  
  1196.  
  1197. /*----------------------------------------------------------------------------
  1198.     IsArrowKey 
  1199.     
  1200.     Check to see if a character is an arrow key.
  1201.             
  1202.     Entry:    theChar = character.
  1203.                 
  1204.     Exit:    function result = true if arrow key.
  1205. ----------------------------------------------------------------------------*/
  1206.  
  1207. Boolean IsArrowKey (char theChar)
  1208. {
  1209.     return theChar == upArrow || theChar == downArrow ||
  1210.         theChar == leftArrow || theChar == rightArrow;
  1211. }
  1212.  
  1213.  
  1214.  
  1215. /*----------------------------------------------------------------------------
  1216.     MyTEClick
  1217.     
  1218.     Handle a click in a textedit field.
  1219.     
  1220.     Entry:    thePt = location of click in local coords.
  1221.             extend = true to extend selection (shift key down).
  1222.             hTE = handle to textedit record.
  1223.             
  1224.     This function is identical to the standard TEClick function, except it
  1225.     contains a hack to handle a problem with clickloop functions in native
  1226.     mode. The hack is similar to the one used in the SetListClickLoop
  1227.     function in listutil.c.
  1228.     
  1229.     The problem is that textedit clickloop functions return their Boolean
  1230.     function result using the Z bit in the CC register. This doesn't 
  1231.     work properly with the Mixed Mode manager. So we interpolate some
  1232.     68K glue code to always return with the Z bit clear, indicating a
  1233.     fucntion result of true. All the clickloop functions in NewsWatcher
  1234.     return true. If a clickloop function needs to return false, this
  1235.     function won't work properly.
  1236.     
  1237.     Apple has been mucking with the TextEdit.h header file definition for
  1238.     uppTEClickLoopProcInfo. In some versions of the universal headers, this
  1239.     is defined with the function result returned in D0, and with those
  1240.     versions, this hack is not needed. In other versions, it's defined with 
  1241.     the function result returned in the Z bit, and with those versions, we
  1242.     need the hack. For NewsWatcher's purposes, this hack works with both 
  1243.     versions, so I'm keeping it.
  1244. ----------------------------------------------------------------------------*/
  1245.  
  1246. void MyTEClick (Point thePt, Boolean extend, TEHandle hTE)
  1247. {
  1248. #ifdef powerc
  1249.  
  1250.     #pragma options align=mac68k
  1251.     static struct clickLoopGlue {
  1252.         long move;                            /* movea.l clickLoopUPP,a0 */
  1253.         short jsr;                            /* jsr (a0) */
  1254.         short mvq;                            /* moveq #1,d0 - clears CC Z bit to return true */
  1255.         short rts;                            /* rts */
  1256.         TEClickLoopUPP clickLoopUPP;        /* the UPP */
  1257.     } clickLoop68K = {
  1258.         0x207A0008,
  1259.         0x4E90,
  1260.         0x7001,
  1261.         0x4E75,
  1262.         0
  1263.     };
  1264.     #pragma options align=reset
  1265.     
  1266.     
  1267.     if ((**hTE).clickLoop == nil) {
  1268.         TEClick(thePt, extend, hTE);
  1269.     } else {
  1270.         clickLoop68K.clickLoopUPP = (**hTE).clickLoop;
  1271.         (**hTE).clickLoop = (TEClickLoopUPP)&clickLoop68K;
  1272.         TEClick(thePt, extend, hTE);
  1273.         (**hTE).clickLoop = clickLoop68K.clickLoopUPP;
  1274.     }
  1275.     
  1276. #else
  1277.  
  1278.     TEClick(thePt, extend, hTE);
  1279.     
  1280. #endif
  1281. }